<?php

namespace App\Services\Wallet;

use App\Constant\Enum;
use App\Exceptions\AdrewardReceiveException;
use App\Models\Admodel;
use App\Models\Adreward;
use App\Models\AdrewardDetail;
use App\Models\GroupApply;
use App\Models\ReturnMoneyLog;
use App\Models\Setting;
use App\Models\User;
use App\Models\UserWallet;
use App\Services\Bookkeeper;
use App\Services\User\BlockUserService;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use PHPUnit\Util\Exception;

class AdrewardService
{
    /**
     * @param User $user
     * @param array $details
     * @return UserWallet
     * @throws AdrewardReceiveException
     */
    public static function upload(User $user, array $details = []): array
    {
        if (!self::checkIsNotReplay($user)) {
            $headers = array_map(function ($value) {
                return is_array($value) ? implode(', ', $value) : $value;
            }, request()->headers->all());
            Log::error(json_encode([
                'user' => $user->id,
                'body' => request()->all(),
                'header' => $headers,
            ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
            throw new Exception('Not Accept!');
        }

        $userIp = request()->ip();
        $maxPacketAmount = Setting::get('max_redpack_amount');
        if (empty($details)) {
            if ($user->is_new) {
                $coins = intval(Setting::get('coin_starter'));
                if ($coins > 0) {
                    return [
                        'coins' => $coins,
                    ];
                } else {
                    throw new AdrewardReceiveException('请求太快了，请休息下～');
                }
            } else {
                $amount = AdrewardDetail::query()
                    ->where([
                        'user_id' => $user->getAttribute('id'),
                        'collect_status' => Enum::ADREWARD_DETAIL_COLLECT_STATUS_PENDING
                    ])
                    ->sum('amount');
                if ($amount > 0) {
                    return [
                        'coins' => (int)min($amount, $maxPacketAmount),
                    ];
                } else {
                    throw new AdrewardReceiveException('请求太快了，请休息下～');
                }
            }
        }
        $exchangeRate = Setting::get('exchange_rate') ?? 0;
        $maxEcpm = Setting::get('max_ecpm') ?? 0;
        $invitesSetting = Setting::get('invites');
        $minimum_money = isset($invitesSetting['minimum_money']) ? intval($invitesSetting['minimum_money']) : 0;//最低激励广告金币

        if (!$user->is_show_ad) {
            throw new AdrewardReceiveException("奖励正在准备中，请稍后在再试！");
        }

        DB::beginTransaction();
        $isAbnormal = false;
        try {
            $rewardDetails = [];
            foreach ($details as &$detail) {
                $record = [];
                $admodel = Admodel::query()
                    ->with('adtype:id,reward_rate,name')
                    ->select(['id', 'adtype_id', 'value', 'override_reward_rate'])
                    ->find($detail['admodel_id'] ?? -1);
                if (!$admodel) {
                    throw new AdrewardReceiveException('无效模型数据，稍后再试!');
                }
             //   $rewardRate = $admodel->override_reward_rate <= 0 ? $admodel->adtype->reward_rate : $admodel->override_reward_rate;
                $rewardRate = $admodel->override_reward_rate;
                try {
                    $timestamp = substr($detail['displayed_at'], 0, 10);
                    $displayedAt = Carbon::createFromTimestamp($timestamp);
                } catch (\Exception $exception) {
                    throw new AdrewardReceiveException('频繁请求，请休息下～');
                }
                $ecpm = $detail['ecpm'] ?? 0;
                if ($maxEcpm > 0 && $ecpm > $maxEcpm) {
                    // 直接忽略超过限额的记录
                    $isAbnormal = true;
                    continue;
                }
                $realMoney = Bookkeeper::calculate($ecpm);
                if ($minimum_money && $admodel->getAttribute('position') == "stimulate" && $minimum_money > $ecpm) {

                    $realMoney = round($minimum_money / (1000 * 100), 4);
                }
                $userMoney = sprintf('%.4f', $realMoney * $rewardRate);
                $coins = (int)($userMoney * $exchangeRate);
                $record['user_id'] = $user->getAttribute('id');
                $record['adtype_id'] = $admodel->getAttribute('adtype_id');
                $record['adtype_name'] = $admodel->adtype->name;
                $record['admodel_id'] = $admodel->getAttribute('id');
                $record['admodel_value'] = $admodel->getAttribute('value');
                $record['amount'] = $coins;
                $record['real_money'] = $realMoney;
                $record['user_money'] = $userMoney;
                $record['reward_rate'] = $rewardRate;
                $record['exchange_rate'] = $exchangeRate;
                $record['ecpm'] = $ecpm;
                $record['appcode'] = request()->header('APPCODE') ?? 'unknown';
                $record['collect_status'] = Enum::ADREWARD_DETAIL_COLLECT_STATUS_PENDING;
                $record['user_ip'] = $userIp;
                $record['displayed_at'] = $displayedAt;
                $record['created_at'] = Carbon::now();
                $record['updated_at'] = Carbon::now();
                $record['hashid'] = AdrewardDetail::hashid($record);
                $record['adplatform_name'] = isset($detail['adplatform_name']) ? $detail['adplatform_name'] : "";
                // 检查广告展示时间
                /** @var Carbon $displayedAt */
                if ($displayedAt->lessThan($user->getAttribute('registered_at'))) {
                    // throw new AdrewardReceiveException('4003#频繁领取红包，请休息下～');
                    continue;
                }

                $rewardDetails[] = $record;
            }

            // 检查提交数据是有重复刷量
            $ads = array_unique(array_column($rewardDetails, 'hashid'));
            if (count($ads) != count($rewardDetails)) {
                throw new AdrewardReceiveException('4001#频繁领取红包，请休息下～');
            }
            // 检查数据酷中是否有重复数据
            $has = AdrewardDetail::query()->whereIn('hashid', $ads)->select(['hashid'])->exists();
            if ($has) {
                throw new AdrewardReceiveException('4002#频繁领取红包，请休息下～');
            }
            // 禁用客户端拉取广告
            if ($isAbnormal) {
                BlockUserService::ban($user, [
                    'status' => Enum::STATUS_BLOCKED,
                    'is_show_ad' => 0,
                    'remark' => 'ECPM值过高超过限制，系统禁用广告'
                ]);
            }

            AdrewardDetail::query()->insert($rewardDetails);
            DB::commit();

            $amount = AdrewardDetail::query()
                ->where([
                    'user_id' => $user->getAttribute('id'),
                    'collect_status' => Enum::ADREWARD_DETAIL_COLLECT_STATUS_PENDING
                ])
                ->sum('amount');

            if ($user->is_new) {
                $coins = intval(Setting::get('coin_starter'));
                if ($coins > 0) {
                    return [
                        'coins' => $coins,
                    ];
                }
            }

            return [
                'coins' => (int)min($amount, $maxPacketAmount),
            ];
        } catch (\Exception $exception) {
            DB::rollBack();
            throw $exception;
        }
    }

    /**
     * @param User $user
     * @param array $details
     * @return UserWallet
     */
    public static function receive(User $user): void
    {
        $maxPacketAmount = Setting::get('max_redpack_amount');//领取红包最大值
        DB::beginTransaction();
        try {
            //红包收益记录
            $details = AdrewardDetail::query()
                ->where([
                    'user_id' => $user->getAttribute('id'),
                    'collect_status' => Enum::ADREWARD_DETAIL_COLLECT_STATUS_PENDING,
                ])
                ->select(['id', 'user_id', 'adreward_id', 'amount', 'real_money', 'user_money', 'collect_status', 'adplatform_name', 'adtype_id'])
                ->latest()
                ->get();
//
            if ($user->is_new) {
                // 不需要依赖明细
                $coins = intval(Setting::get('coin_starter'));//首次红包奖励
                $exchangeRate = Setting::get('exchange_rate') ?? 1;
                $money = sprintf('%.4f', $coins / $exchangeRate);
                if ($coins > 0) {
                    $reward = [
                        'user_id' => $user->getAttribute('id'),
                        'amount' => $coins,
                        'real_amount' => $coins,
                        'real_money' => $money,
                        'user_money' => $money,
                        'received_at' => Carbon::now(),
                    ];
                    /** @var Adreward $rewardModel */
                    $rewardModel = Adreward::query()->create($reward);
                    $record = [];
                    $record['adreward_id'] = $rewardModel->getAttribute('id');
                    $record['user_id'] = $user->getAttribute('id');
                    $record['adtype_id'] = 0;
                    $record['adtype_name'] = '首次红包奖励';
                    $record['admodel_id'] = 0;
                    $record['admodel_value'] = '首次红包奖励';
                    $record['amount'] = $coins;
                    $record['real_money'] = $money;
                    $record['user_money'] = $money;
                    $record['reward_rate'] = 1;
                    $record['exchange_rate'] = $exchangeRate;
                    $record['ecpm'] = 0;
                    $record['appcode'] = request()->header('APPCODE') ?? 'unknown';
                    $record['collect_status'] = Enum::ADREWARD_DETAIL_COLLECT_STATUS_APPROVED;
                    $record['displayed_at'] = Carbon::now();
                    $record['created_at'] = Carbon::now();
                    $record['updated_at'] = Carbon::now();
                    AdrewardDetail::query()->create($record);

                    UserWalletService::income($user, $coins, Enum::WALLET_TYPE_REWARD);
                    User::query()
                        ->where([
                            'id' => $user->getAttribute('id')
                        ])
                        ->update([
                            'is_new' => 0
                        ]);
                    DB::commit();
                    return;
                }
            }

            if ($details->isEmpty()) {
                throw new AdrewardReceiveException('红包已被领取，请稍后再试');
            }

            $rewards = [];
            $returnMoneyLogs = [];
            foreach ($details as $detail) {
                if (!empty($rewards)) {
                    $total = array_sum(array_column($rewards, 'amount'));
                    if ($total >= $maxPacketAmount) {
                        break;
                    }
                }
                $rewards[] = $detail->toArray();
//激励视频广告
                if ($detail->adtype_id == 2) {

                    $returnMoneyLogs[] = [
                        "uid" => $detail->user_id,
                        "adreward_detail_id" => $detail->id,
                        "create_at" => Carbon::now(),
                        "status" => 1
                    ];
                }

            }

            $group_apply = GroupApply::where('user_id', $user->id)->where('status', 'approved')->where('is_pay', 1)->first();
//有待领取的激励视频   高级团长未到期 团长返现
            if ($returnMoneyLogs && $group_apply && strtotime("+ " . $group_apply->days . " days", strtotime($group_apply->created_at)) >= Carbon::today()->endOfDay()->timestamp) {

                $limit = intval($group_apply->price / $group_apply->days / $group_apply->amount);
                $finished_num = ReturnMoneyLog::where('create_at', '>', Carbon::today()->startOfDay())->where('status', 1)->count();
                $left_limit = $limit - $finished_num;

                if ($left_limit) {

                    if ($left_limit <= sizeof($returnMoneyLogs)) {

                        $returnMoneyLogs = array_slice($returnMoneyLogs, 0, $left_limit);
                    }

                    ReturnMoneyLog::insert($returnMoneyLogs);
                    /*$return_coins = intval($group_apply['amount'] * $exchangeRate);
                    $return_money = $group_apply['amount'];
                    $reward = [
                        'user_id' => $group_apply->getAttribute('user_id'),
                        'amount' => $return_coins,
                        'real_amount' => $return_coins,
                        'real_money' => $return_money,
                        'user_money' => $return_money,
                        'received_at' => Carbon::now(),
                    ];
                    Adreward::query()->create($reward);
                    UserWalletService::income($user, $return_coins, Enum::WALLET_TYPE_REWARD);*/
                }

            }
            $realAmount = array_sum(array_column($rewards, 'amount'));
            $amount = (int)min($realAmount, $maxPacketAmount);

            $reward = [
                'amount' => $amount,
                'real_amount' => (int)floor($realAmount),
                'real_money' => array_sum(array_column($rewards, 'real_money')),
                'user_money' => array_sum(array_column($rewards, 'user_money')),
                'received_at' => Carbon::now(),
            ];
            /** @var Adreward $rewardModel */
            $rewardModel = $user->adrewards()->create($reward);
            AdrewardDetail::query()->whereIn('id', array_column($rewards, 'id'))
                ->update([
                    'adreward_id' => $rewardModel->getAttribute('id'),
                    'collect_status' => Enum::ADREWARD_DETAIL_COLLECT_STATUS_APPROVED,
                ]);
            UserWalletService::income($user, $amount, Enum::WALLET_TYPE_REWARD);

            DB::commit();
        } catch (\Exception $exception) {
            DB::rollBack();
            throw $exception;
        }
    }


    public static function checkIsNotReplay(User $user): bool
    {
        $data = request()->post();
        $token = $data['token'] ?? '';
        $timestamp = $data['timestamp'] ?? '';
        $signature = $data['signature'] ?? '';

        if (empty($token) || empty($timestamp) || empty($signature)) {
            return false;
        }

        // KeySort.
        $details = $data['details'] ?? '[]';
        if (!is_array($details)) {
            $details = json_decode($details, true);
        }
        $sorted = [];
        foreach ($details as $detail) {
            ksort($detail);
            $sorted[] = $detail;
        }
        $content = json_encode($sorted, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        $serverSignature = md5($token . $content . $timestamp);
        $same = $serverSignature == $signature;
        if ($same) {
            $userId = auth('jwt')->id();
            $cacheKey = "tonce:{$userId}:{$token}";
            $cachedToken = Cache::get($cacheKey);
            if ($cachedToken == $token) {
                Log::error(sprintf('TIMENONCE >>> Has cached nonce. key: %s, value: %s', $cacheKey, $cachedToken));
                BlockUserService::ban($user, [
                    'status' => Enum::STATUS_BLOCKED,
                    'is_show_ad' => 0,
                    'remark' => '接口重放攻击: NONCE 重新使用',
                ]);
                return false;
            }
            $now = time();
            $diffs = $now - $timestamp;
            if ($diffs > 300) {
                Log::error(sprintf('TIMENONCE >>> Nonce expired. now: %s, timestamp: %s, diffs: %s', $now, $timestamp, $diffs));
                return false;
            }
            Cache::put($cacheKey, $token, 300);

            return true;
        } else {
            Log::error(sprintf('TIMENONCE >>> Signature not match. server: %s, client: %s', $serverSignature, $signature));
            BlockUserService::ban($user, [
                'status' => Enum::STATUS_BLOCKED,
                'is_show_ad' => 0,
                'remark' => '接口重放攻击: 签名不匹配',
            ]);
            return false;
        }
    }
}
